Please enable JavaScript to view this website.

Skip to main content
Experimental proof of concept

Edge Mesh is a throwaway proof-of-concept / learning experiment built on top of the Greengrass reference implementation. It is not a supported or maintained deliverable and may be removed. The reference implementation itself is long-lived; Edge Mesh is not.

Edge Mesh Architecture

In a nutshell

The mesh is three stacked layers (radio → batman-adv → IP) that each depend on the one below, brought up by three ordered systemd units. The core and the Pis are both mesh nodes at that level but play different roles above it (Greengrass core vs. client device). Read this if: you want the conceptual map of how the mesh is built and how the software on top is arranged. Before this: skim the Overview.

This page is the conceptual map of how the mesh is built and how the software on top of it is arranged. For the authoritative, code-adjacent version, see tools/mesh/ARCHITECTURE.md in the iot-edge repo. It carries the full glossary and the deep rationale.

The three-layer model

The mesh is built in three stacked layers. Each layer only works if the one below it is already up. That ordering is important to understand, and it is why there are three separate systemd units (see the boot chain below).

  • L1, the radio. A WiFi interface is taken out of normal "managed" mode and put into IBSS (Independent Basic Service Set) mode. This makes it peer-to-peer with no access point. It is then joined to a named cell every node shares. Every node must use identical SSID / channel / BSSID or it lands on a different cell and sees no one. That radio must be an ath9k_htc USB dongle (see Hardware & RF).
  • L2, batman-adv. The mesh-routing protocol, running as a Linux kernel module. It takes the raw radio and exposes a virtual interface, bat0. Behind bat0, batman floods small "originator" beacons (OGMs) so every node learns a route to every other node, measures each link's quality (TQ, 0-255), and forwards frames multi-hop. You never configure a per-route table: that is the whole point of a mesh protocol.
  • L3, IP. Once bat0 exists, it gets a static address in the mesh subnet: mesh-node-N10.88.0.(N+2), and the core at the reserved 10.88.0.1. From here up it is ordinary IP: you ping 10.88.0.3 and the frame rides bat0, batman routes it, it arrives. bat0 is the interface you ping; the raw wlanX is plumbing you mostly forget about.
  • App layer (core only). The health portal runs on the core (itself just another mesh node) and shells out to batctl locally to read what the mesh looks like from the core's vantage point, then serves it locally at /mesh.

Core and Pi roles

The core and the Pis are both mesh nodes at L1 to L3, but they play very different roles above that.

RoleCore (CompuLab iMX8, 10.88.0.1)Pi mesh-node (10.88.0.2.6)
Greengrass roleCore (runs the Greengrass nucleus + components)Client device (authenticates to the core's broker; not a Greengrass core)
Cellular uplinkYes, the only path to AWSNo
Local MQTTRuns the Moquette broker + Bridge (relays mesh topics ⇄ AWS IoT Core)Connects to the core's Moquette broker at 10.88.0.1:8883
BLE scanningble-scanner component (subprocess-per-scan, BlueZ 5.50 workaround)pi/scanner.py (systemd service)
Publishingasset-publisher Greengrass componentpi/publisher.py (systemd service)
Health / maphealth-portal (reads batctl, draws the map)pi/mesh_reporter.py reports its neighbor view to the portal (systemd service)
Software deliveryGreengrass cloud/local deploymentImaging + tools/provision-pi.sh (not a Greengrass deployment)

Each Pi maps to one IoT thing named by its MPBID (mesh-Pi MPBIDs begin with FFFC, e.g. FFFC000101). Because Greengrass Client Device Auth (CDA) ties the MQTT client id to the thing name, a Pi holds exactly one authenticated Moquette connection, shared by scanning and EdgeMesh relay.

The Pi boot chain

The three mesh layers map one-to-one onto three systemd units, rendered by tools/provision-pi.sh. They form an ordered chain because each layer depends on the one below it already being up:

All three are Type=oneshot with RemainAfterExit=yes: they do their setup once at boot and then "stay active" so systemd treats the layer as established. NetworkManager is told to ignore the mesh radio (a 99-mesh-unmanaged.conf drop-in) so it does not fight these units for control of the interface.

On a Pi, the client-device units stack on top of that chain:

  • pi-provision.service (oneshot): After/Requires=mesh-ip.service; obtains the operational cert on first boot, then is skipped once the cert exists.
  • pi-publisher.service (simple, Restart=always): After/Requires=pi-provision.service; runs the scan-and-publish loop and hosts the EdgeMesh relay.
  • pi-mesh-reporter.service (simple): reports batman neighbors to the core health portal.

How the portal reads the mesh: three topology sources

The map you see in the health portal is the mesh as the core sees it. health-portal's mesh.py can build the topology graph from three sources:

SourceWhenWhat it shows
core-batctlLive read of batctl o / batctl n + ip neigh from the core.A core-rooted routing tree: only what the core can hear, with its best-route TQ and next hop to each node. Not full node-to-node adjacency.
node-reportsEach node (pi/mesh_reporter.py) POSTs what it hears to /mesh/node-report; read_topology merges those reports (within NODE_REPORT_TTL_S) into the core-batctl tree.Enriched Pi-to-Pi adjacency: relay-link TQ and per-node neighbors the core can't see alone.
fakeDev / demo, when MESH_FIXTURE_DIR points at a committed graph.json.A full pre-baked adjacency graph, so the UI can be exercised without hardware during development.

Where the code lives